# take in a msl file, split it into messages

class MuslParser
  def initialize(input_file=nil, component_name="COMPONENT", output_dir=".")
    return if input_file.nil?
    @output_dir = output_dir
    @component = component_name
    @debug = true

    puts "component = #{@component}"
    puts "output_dir = #{@output_dir}"
    puts "------ OUTPUT USAGE -------"
    puts "use the output base.rb file for your base class"
    puts "use the #{@component}_message_*.txt files for your message class content"
    puts "------ ------------ -------"

    arr = get_file(input_file)
    parse_file(arr)

    # build and write out a base class from the header portion
    make_base_class_file(@header)

    # write out each message to its own file (skip the last one (would be "footer")
    @messages.each do | k, v |
      if k.to_i < @messages.size
        file = "#{@output_dir}/#{@component}_message_#{k}.txt"
        v = fix_counters(v)
        File.open(file, 'w') do |f|
          v.each do | s |
            f.puts s
          end # v
        end  # File.open
      end # < @messages.size
    end # @messages.each
  end

  def get_file(args)
   if !args.is_a?(Array)
     arr = []
     f = File.open(args, "r")
     f.each_line do |line|
       arr.push line
     end
     return arr
   else
     return args
   end
 end

  def parse_file(arr)
    @header = []
    @messages = Hash.new
    @footer = []
    @count = 1
    header_done = false

    # first pass, separate into header and messages
    arr.each do | a |
       if header_done == false
         if !a.include?("client_send") and !a.include?("server_send")
           @header << a
         else  # first time we come across a client_send or server_send, we're done with the header          
           header_done = true
           # delete last line of header array if it begins with a #
           len = @header.length
           if @header[len-1].include?("#")
             @saved_line = @header[len-1]
             @header.delete_at(len-1)
             break
           end
         end # if a.include
       end # if header.done
    end # arr.each

    arr = arr - @header

    arr_index= 0
    @added = []
    # break the rest into messages
    arr.each do | a |
       @messages[@count.to_s] = [] if @messages[@count.to_s].nil? # make a new array if there is none
       
       if !a.include?("client_receive") and !a.include?("server_receive")

         if a.include?("client_send") or a.include?("server_send")
           a = do_send_sub(a)
         end
     
         if !@added.include?(arr_index)
           @messages[@count.to_s] << a
         end

       else # it's the last line of this message, add it, then increment count to create the next array

        if a.include?("client_receive")
          a = do_receive_sub(a, arr, true)
        else
          a = do_receive_sub(a, arr, false)
        end

         @messages[@count.to_s] << a
         @count = @count + 1
       end # if !a.include?
     arr_index = arr_index + 1
    end   # arr.each
  end

  def do_send_sub(line)
    counter_string = "<%= @step %>"
    transport_string = "<%= @transport %>"
    p = line.split("=")
    first = p[0].strip
    second = p[1]
    suffix = ""
 
    if !first.index("_").nil? 
      suffix = first[first.rindex("_") + 1, first.length].strip
    end
    if is_a_number?(suffix)
      new_first = first[0, first.rindex("_") + 1] + counter_string
    else
      new_first = first + "_" + counter_string
    end

    # replace transport name with erb transport variable
    
    sec = second.split(".")
    new_second = transport_string + "." + sec[1]

    return new_first + " = " + new_second
  end

  def do_receive_sub(line, arr, check_assertions=false)
    counter_string = "<%= @step %>"
    p = line.split("=")
    first = p[0].strip
    second = p[1]

    suffix = first[first.rindex("_") + 1, first.length].strip
    if is_a_number?(suffix)
      new_first = first[0, first.rindex("_") + 1] + counter_string
    else
      new_first = first + "_" + counter_string
    end

    suffix = ""
    sec = second.split(".")
    if !sec[0].index("_").nil?
      suffix = sec[0][sec[0].rindex("_") + 1, sec[0].length].strip
    end
    if is_a_number?(suffix)
      new_sec0 = sec[0][0, sec[0].rindex("_") + 1] + counter_string
    else
      new_sec0 = sec[0] + "_" + counter_string
    end

    new_line = new_first + " =" + new_sec0 + "." + sec[1]

    added_lines = []
    if check_assertions == true
      new_line, added_lines = add_assertions_and_variables(line, new_line, arr)
      added_lines.each do | a |
       new_line = new_line + "\n" + a
     end
    end
    return new_line

  end

  def add_assertions_and_variables(line, new_line, arr)
    new_arr = []
    add_to_new_arr = false
    arr_index = 0
    arr.each do | a |
#       @debug and puts "a   : #{a}"
#       @debug and puts "line: #{line}"

      if a == line and !a.index("{").nil?
#        @debug and puts "add to new arr starts here, #{a}"
        add_to_new_arr = true
        next
      end

      if add_to_new_arr == true and ( !a.index("server_send").nil? or !a.index("client_send").nil?)
        add_to_new_arr = false
        # @debug and puts "STOP ! #{a}"
        new_arr.delete_at(new_arr.length - 1) # lose the last line
        @added.delete_at(@added.length - 1)
        return new_line, new_arr
      end

      if add_to_new_arr == true and a.index("server_send").nil? and a.index("client_send").nil?
        # @debug and puts "adding: #{a}"
        @added << arr_index
        new_arr << a
      end
      arr_index = arr_index + 1
    end


    return new_line, new_arr

  end

  def is_a_number?(s)
      s.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
  end

  # look for any of: fields to find strings like this "of: xxx_N)"
  # and change their values to xxx_<%= @step %>
  def fix_counters(arr)
    # first pass, look for "of :" fields, add their references to vars array
    vars = Array.new
    arr.each do | a |
      if a.index("of: ")
        idx = a.index("of: ") + 4
        idx2 = a.index(")") - 1
        var = a[idx..idx2]
        @debug and puts "=> found counter field: " + var
        vars << var
      end
    end

    # second pass, replace all fields'number values as needed with "<%= @step %>"
    new_arr = Array.new
    arr.each do | a |
      vars.each do | v |
        if a.index(v)
          idx = a.index(v)
          eq = a.index("=") # is there an equals sign?
          if eq.nil?
            idx2 = a.rindex("_", a.length()) + 1 # if not, look across the word
          else
            idx2 = a.rindex("_", eq) + 1  # if there is an equals sign, only look that far
          end
          field_name = a[idx..idx2 - 1]
          #@debug and puts "found counter field: " + field_name

          if eq.nil?
            fidx = a.rindex(")")
            if fidx.nil?
              fidx = a.index(" ", idx)
             # @debug and puts fidx
            end
            fidx = fidx - 1
          else
            fidx = eq - 2 # remove white space and equals sign
          end
          
          portion = a[idx..fidx]
          #@debug and puts "portion = " + portion

          suffix = a[idx2..fidx]
         # @debug and puts "suffix = " + suffix
          if is_a_number?(suffix)
            new_portion = field_name + "<%= @step %>"
            a.gsub!(portion, new_portion)
            @debug and puts "<= replaced counter field: " + a
          end # if is_a_number?
        end # if.index(v)
      end # vars.each
      new_arr << a
    end  # each a

    return new_arr
  end

  # parse the header text file and output a msul builder component base class file
  def make_base_class_file(header)
    # mkdir @component if necessary
    # output_file = "#{@component}/base.rb"
    output_file = "base.rb"

    contents = Array.new
    contents << "require 'erb'"
    contents << "require 'common/base'"
    contents << ""
    contents << "class #{@component}Base < Base"
    contents << ""
    contents << "  attr_accessor :step, :transport, :base_options, :base_transports, :base_variables, :message"
    contents << '  def initialize(step, filename, params = {}, transport="DEFAULT")'
    contents << "   super()"
    contents << "   @transport = transport"
    contents << "   @step = step"
    contents << ""

    options = Array.new
    variables = Array.new
    transports = Array.new

    # tease out the global options, variables and transports
    header.each do | l |
       str = l.strip.chomp  # (get rid of white space and carriage return)    
       if str.index("$") == 0
         options <<  str
       elsif str.index("@") == 0
         variables <<  str
       elsif str.index("tcp(") or
             str.index("udp(") or
             str.index("ethernet(") or
             str.index("ip(")
         transports <<  str
       end
    end

    default_transport = transports[0].split("=")[0].strip

    # format the global options
    param_strs = Array.new
    base_options_strs = Array.new
    options.each do | o |
      p = o.split("=")
      option_name = p[0][1, p[0].length - 1] # stripping off the '$'
      option_name.strip!
      param_strs << "   @" + option_name + " = params[\"" + option_name + "\"] or \"\""
      base_options_strs << "   $" + option_name + " = '<%= @" + option_name +" %>'"
    end

    param_strs.each do | p |
      contents << p
    end
    contents << ""

    contents << "   @base_options = \""
    base_options_strs.each do | p |
      contents << p
    end
    contents << "   \""
    contents << ""

    contents << "   @base_variables = \""
    variables.each do | v |
      contents << "  " + v
    end
    contents << "   \""
    contents << ""

    contents << "   @base_transports = '"
    transports.each do | t |
      contents << "   " + t
    end
    contents << "   '"
    contents << ""

    contents << "   if !filename.nil?"
    contents << "     template = get_message(\"#{@component}\", filename)"
    contents << "     erb = ERB.new(template, 0, '%<>')"
    contents << "     @message = erb.result(binding)"
    contents << "   end"
    contents << "  end"
    contents << "end"

    File.open("#{@output_dir}/#{output_file}", 'w') do |f2|
      contents.each do | c |
        if c.index("DEFAULT")
          c.gsub!("DEFAULT", default_transport)
        end
        f2.puts c
      end
    end

  end

end


 ## --------------------------- Command-Line Entry Point ----------------------------


# the command-line executable
if $0 == __FILE__
  if ARGV[1].nil?
    puts "Usage: <required: input_file> <required: component_name> <optional: output_dir>"
  elsif ARGV[2].nil?#  input file and component_name are required args
    fb = MuslParser.new(ARGV[0], ARGV[1])
  elsif ARGV[3].nil? # output_dir is optional third arg
    fb = MuslParser.new(ARGV[0], ARGV[1], ARGV[2])
  end
end

